Today I’m going to review pull_9302

Step 1: discussion

When using a mock and deleting an attribute reset_mock cannot be used anymore since it tries to call reset_mock on the _deleted sentinel value.

from unittest.mock import MagicMock
mock = MagicMock()
mock.a = 'test'
del mock.a
mock.reset_mock()

Gives:

Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
  File "/usr/lib/python3.5/unittest/mock.py", line 544, in reset_mock
    child.reset_mock(visited)
AttributeError: '_SentinelObject' object has no attribute 'reset_mock'

Step 2: commit messages and files changed

commit messages:

Skip deleted attributes while calling reset_mock

files changed:

pull_9302

So what actually reset_mock do?

Step 3: dive into the code

def reset_mock(self,  visited=None,*, return_value=False, side_effect=False):
    "Restore the mock object to its initial state."
    ...

    for child in self._mock_children.values():
        if isinstance(child, _SpecState):
            continue
        child.reset_mock(visited)

    ret = self._mock_return_value
    if _is_instance_mock(ret) and ret is not self:
        ret.reset_mock(visited)

reset_mock(*, return_value=False, side_effect=False)
The reset_mock method resets all the call attributes on a mock object:

>

mock = Mock(return_value=None)
mock(‘hello’)
mock.called
True
mock.reset_mock()
mock.called
False

Step 4: Fixed it

But why we use _deleted instead of None? The PR propose a fix. Before understanding the fix, we have to understand Mock and Sentinel in python.

Mock and MagicMock objects create all attributes and methods as you access them and store details of how they have been used. You can configure them, to specify return values or limit what attributes are available, and then make assertions about how they have been used:

And from Sentinel

Sometimes it is necessary to differentiate between an argument that has not been provided, and an argument provided with the value None. For that purpose, we create what’s called a ‘sentinel value’.

We implement _SentinelObject class:

class _SentinelObject(object):
    "A unique, named, sentinel object."
    def __init__(self, name):
        self.name = name

    def __repr__(self):
        return 'sentinel.%s' % self.name

    def __reduce__(self):
        return 'sentinel.%s' % self.name

...
sentinel = _Sentinel()

DEFAULT = sentinel.DEFAULT
_missing = sentinel.MISSING
_deleted = sentinel.DELETED

After we delete an attr in the code, child become sentinel.DELETED instead of None. So I propose a better test case:

def test_attribute_deletion_reset_mock(self):
    mock = Mock()
    mock.attr = 'test'
    del mock.attr
    mock.reset_mock()
    self.assertFalse(hasattr(mock, 'attr'))

Conclusion

Instead of review PR every day, I found that write topic article depends on the pr/issue would be more helpful to readers. That is what I’m going to do next.